/**

 * An implementation of the file system.

 *

 * Currently, all methods throw a RuntimeException.

 */

import java.util.*;

import java.io.*;

/*

 * CURRENT QUESTIONS:

 * 

 * What is iNumber? Is it the index number in the file table?

 * 

 * Interaction between FD, FT, iNumber, and iNode.

 * 

 */

class JavaFileSystem implements FileSystem {

    // Set up the constants for the whence field in seek

    public static final int SEEK_SET = 0;

    public static final int SEEK_CUR = 1;

    public static final int SEEK_END = 2;

    // public static final Disk disk = new Disk();

    public static final FileTable sysOFT = new FileTable();

    private SuperBlock superblock;

    private Disk disk;

    public IndirectBlock freeList; // INDIRECT BLOCK. First block has pointer

    // to next block.

    public InodeBlock inodeBlock;

    public FileTable ft;

    // public int freeList;

    JavaFileSystem() {

        // throw new RuntimeException("not implemented");

    }

    // Initialize the the disk to a state representing an empty file-system.

    // Fill in the super block, mark all inodes as "unused", and link

    // all data blocks into the free list.

    public int formatDisk(int size, int iSize) {

        disk = new Disk(); // Do we really want to recreate the disk every time

        // we format?

        superblock = new SuperBlock();

        // validate data (size and isize)

        if (!(size > 0 && iSize > 0 && iSize < size)) {

            return -1;

        }

        // Create inode blocks and write to disk

        InodeBlock blocks = new InodeBlock();

        for (int i = 0; i < blocks.node.length; i++) {

            blocks.node[i].flags = 0;

        }

        // write to disk over and over to fill the disk.

        for (int j = 0; j < disk.NUM_BLOCKS; j++) {

            disk.write(j, blocks);

        }

        // build file table list

        ft = new FileTable();

        // initialize free list - suggestion is from bottom up.

        freeList = new IndirectBlock();

        // contains 10 directly addressable blocks.

        // 3 other pointers.

        IndirectBlock B = new IndirectBlock();

        int offset = 12;

        // for number of inodeblocks

        for (int i = size - 1; i >= iSize; i--) /* all but last number */{

            B.pointer[offset--] = i;

            // decrement offset to get 0-12

            if (offset == 0) {

                // write IndirectBlock to disk

                // B.pointer[offset] = last block address;

                disk.write(i, B); // the address of the block.

                if (i != iSize)

                    B.clear();

                offset = 12;

            }

            if (i == iSize) { // or when it hits the end. The math isn't

                // right.

                freeList = B; // will (not) clearing B after this be a memory

                // leak?

                // Cache B as freeList.

            }

        }

        // Initialize superblock and write to disk.

        disk.write(0, superblock);

        return 1;

        // note: isize does not include superblock

        /**
         * 
         * page 463: Grouping. A modification of the free-list approach.
         * 
         * 
         * 
         * For i in INode list: setFlag(0) for each data block: freeList.add()
         * 
         * 
         */

    } // formatDisk

    // Close all files and shut down the simulated disk.

    public int shutdown() {

        // Close all the files that are valid in the FT...look at file table &

        // use existing commands

        for (int i = 0; i < ft.MAX_FILES; i++) {

            if (ft.bitmap[i] != 0) {

                int c = close(i);

                if (c == -1) {

                    error("closing file in shutdown");

                    return -1;

                }

            }

        }

        // sync freelist...involves a write()

        disk.write(superblock.freeList, freeList);

        // sync superblock...involves a write()

        disk.write(0, superblock);

        // stop disk

        disk.stop();

        // throw new RuntimeException("not implemented");

        return 1;

    } // shutdown

    // Create a new (empty) file and return a file descriptor.

    public int create() {

        // is inumber the index number fd in the file table?

        // file discriptor...return the entry in the FT

        // allocate FT entry

        int inumber = ft.allocate();

        // if not allocateable...return error (-1 and print)...list no room

        if (inumber == -1) {

            error("No more room in file table");

            return inumber;

        }

        // build new inode...allocate...write out to disk

        Inode in = new Inode();

        FileDescriptor fd = new FileDescriptor(in, inumber);

        allocateInode(in); // Get the address of a free iNodeBlock and thus

        // iNode on the disk. Update FreeList.

        // disk.write(blocknum, buffer)

        // call open()...return FD

        open(inumber);

        return inumber;

    } // create

    // Return the inumber of an open file

    public int inumber(int fd) {

        // takes in FD...ensure FD is valid...return error otherwise

        if (ft.isValid(fd)) {

            return ft.getInumber(fd);

        } else {

            error("Invalid file descriptor in inumber");

            return -1;

        }

    }

    // Open an existing file identified by its inumber

    public int open(int iNumber) {

        Inode in;

        // check input for validity

        // if null...error

        // otherwise open (inumber and inode)

        // read inode

        if (iNumber >= 0 && iNumber < ft.MAX_FILES) {

            in = readInode(iNumber);

            if (in == null) {

                error("null inode read in open(inumber)");

                return -1;

            } else {

                return open(iNumber, in);

            }

        } else {

            error("invalid iNumber in open(int iNumber)");

            return -1;

        }

        /*
         * 
         * if(NOT in open list){ if(in directory structure){ copy entire FCB
         * 
         * onto empty entry in sys-wide OFT update per-process OFT w/ pointer to
         * 
         * new entry } else{ if( create() allowed){ create() and load into
         * 
         * sys-wide OFT update per process OFT w/ pointer to new entry. } else
         * 
         * return error } }
         */

    } // open

    // Read up to buffer.length bytes from the open file indicated by fd,

    // starting at the current seek pointer, and update the seek pointer.

    // Return the number of bytes read, which may be less than buffer.length

    // if the seek pointer is near the current end of the file.

    // In particular, return 0 if the seek pointer is greater than or

    // equal to the size of the file.

    public int read(int fd, byte[] buffer) {

        int seekPtr;

        int bytesToRead = buffer.length;

        int block = 0; // WTF?!

        Inode inode;

        // check for validity of input

        if (!ft.isValid(fd)) {

            error("Bad file descriptor in read(fd, buffer");

            return -1;

        }

        // get value of seek pointer...done thru FT

        seekPtr = ft.getSeekPointer(fd);

        // ignore empty buffers

        if (bytesToRead < 1) {

            error("Empty Buffer in read(fd, buffer");

            return -1;

        }

        // get the inode at that pt in the FT

        inode = ft.getInode(fd);

        // handle read...loop of some kind (while)...find disk number using

        // getBlock()

        // handle error at this pt. if getBlock() has an error

        boolean temp = true;

        while (temp) {

            // is fileSize what we want? is inodenumber what we want?

            block = getBlock(inode, inodenumber(ft.getInumber(fd),
                    inode.fileSize));

            if (block == -1) {

                error("Error reading block in read(fd, buffer");

                return -1;

            }

            // determine # of bytes to be read

            // bytesToRead = buffer.length;

            // determine actual read using read() function from disk

            disk.read(block, buffer);

            // do something about the buffer at this point (HINT HINT - Thanks
            // Alan

            // /sarcasm)

            // increment the position on the disk itself (seek pointer?)

            temp = false;

        }

        // return the buffer position...represents the end point of the buffer.

        // Do something if there isn't room in the buffer.

        /**
         * 
         * get fd.location int bytesRead = 0;
         * 
         * 
         * 
         * if(seek pointer >= fd.size(){ return 0; else{ for(i = fd.location;
         * i<=
         * 
         * buffer.length; i++){ if(end of file) return bytesRead else{ move
         * 
         * current seek pointer forward bytesRead ++ } } } }
         * 
         * 
         * 
         * 
         */

        return 0;

    } // read

    // Transfer buffer.length bytes from the buffer to the file, starting

    // at the current seek pointer, and add buffer.length to the seek pointer.

    public int write(int fd, byte[] buffer) {

        // check inputs for validity

        // ignore empty buffers

        // get value of seek pointer and get inode

        // figure out the starting pt on the disk

        // handle the write

        // determine the disk # of the block is and allocate if needed

        // deal with errors...

        // deal with holes...ie file gaps

        // determine # of bytes to write from buffer

        // determine if finished writting outside of current file

        // determine if need to read block before writing (hint from

        // book...something to do with appending things)

        // prep data to perform the write (handle any necessary reads)

        // handle actual write (look @ disk)

        // increment position on the disk and other stuff...inode.java

        // update seek

        // update file size (inode)

        // return buffer position

        return 0;

    } // write

    // Update the seek pointer by offset, according to whence.

    // Return the new value of the seek pointer.

    // If the new seek pointer would be negative, leave it unchanged

    // and return -1.

    public int seek(int fd, int offset, int whence) {

        // update seek pointer from whence - one of three values 0,1,2

        // handle error state

        // see comment below.

        // check for validity of inputs

        // catch an invalid whence constant

        // if negative seek pointer return error

        // set seek pointer and return what that pointer was.

        /**
         * 
         * 
         * 
         * int value = current seek pointer value;
         * 
         * 
         * 
         * if(whence == SEEK_CURR) seek = seek + offset else if(whence ==
         * 
         * SEEK_END) seek = end of current + offset else if( whence == SEEK_SET)
         * 
         * seek = offset return value;
         * 
         * 
         */

        return 0;

    } // seek

    // Write the inode back to disk and free the file table entry

    public int close(int fd) {

        int inumber;

        Inode in;

        // check for validity of FD

        if (ft.isValid(fd)) {

            inumber = ft.getInumber(fd);

            in = ft.getInode(fd);

        } else {

            error("Invalid file descriptor in close");

            return -1;

        }

        // get inumber and inode (through file table?)

        // free the Fd thru FT

        ft.free(fd);

        // update inode by write.inode() returns and int...return that int

        int dummyBlock = 0;

        int dummyOffset = 0;

        return writeInode(inodenumber(dummyBlock, dummyOffset), in);

        // return 0;

    } // close

    // Delete the file with the given inumber, freeing all of its blocks.

    public int delete(int iNumber) {

        // check that inode that is return is valid if flag is 0 it is free

        // get size of the file in terms of blocks

        // deallocate all the data blocks...call freeblock()

        // may need to deal with blocks on multiple levels...indirect blocks

        // flag the inode that is it free and write it back to the disk.

        int fd;

        Inode in;

        // check validity of iNumber

        if (iNumber >= 0 && iNumber < ft.MAX_FILES) {

            // check to see if file is open...call to FT

            fd = ft.getFDfromInumber(iNumber);

            // if not open, get inode....read.inode();

            if (fd == -1) {

                int dummyBlock = 0;

                int dummyOffset = 0;

                in = readInode(inodenumber(dummyBlock, dummyOffset));

                // check for error

                if (in == null) {

                    error("bad file in delete");

                    return -1;

                }

                // in = ft.getInode(fd);

            } else {

                error("invalid iNumber in delete");

                return -1;

            }

        }

        return 0;

        /**
         * 
         * while(opencount != 0){ close()
         * 
         * 
         * 
         * delete any FCB or pointers to file.
         * 
         * 
         * 
         * set blocks as empty in FT
         * 
         * 
         * 
         * 
         */

    } // delete

    public String toString() {

        throw new RuntimeException("not implemented");

        // should not do anything...so says Alan

        // String s = "";

        // s += superblock.toString();

        // s += disk.generateStats();

        // s += indblock.toString();

        // s += inodeBlock.toString();

        // return s;

    }

    /** ALAN'S HELPER FUNCTIONS */

    // deals with outputting error messages
    public void error(String s) {

        System.out.println("[Error]  " + s);

    }

    // Finds and allocates next inode. Return -1 if none free.

    // Otherwise return index of the free Inode.

    public int allocateInode(Inode n) {

        /**
         * 
         * Allocate inode: + go through inode section of disk by going from 1 to
         * 
         * isize + read each block off disk + for each block on dick, check
         * 
         * flags of every entry until free entry is found + return inode of that
         * 
         * block (call innodebumber() + if get through both for loops and didn't
         * 
         * find a free file disk is full, can't allocate, error() and return -1.
         */

        return 0;

    }

    // returns in (iblock-1)*disk.blocksize/inodesize + offset+1

    // gives the absolute inode number

    // takes in iblock(inode block number) and ioffset(inode offset)

    public int inodenumber(int iblock, int ioffset) {

        return (iblock - 1) * (Disk.BLOCK_SIZE) / (Inode.SIZE) + ioffset + 1;

    }

    // takes in an inumber and inode. returns an integer

    public int open(int inum, Inode in) {

        /**
         * 
         * + check if Inode is valid (flag !=0) + check if file is already open
         * 
         * by calling file table using inum, which is the index of the file
         * 
         * table entry + if file table entry already exists, file is already
         * 
         * open, error() + if both tests ok, actually open file + create file
         * 
         * descriptor (int) by calling filetable.allocate() + make sure
         * 
         * descriptor we et is within bounds(between 0 and max num in file
         * 
         * table)- error() + add info we have: inode, inum, fd to the file table
         * 
         * by calling add() + add() returns a status value, check to make sure
         * 
         * not less than 0 + if it is, error(), else return file descriptor
         */

        return 0;

    }

    // Returns inode and takes in an integer inode (inode num of file to be

    // read)

    public Inode readInode(int inode) {

        /**
         * 
         * + check if inode is >0 + if is, error() and return NULL + else get
         * 
         * inode block num by call to iblock() + check iblock is >=1 but less
         * 
         * than isize + creat new inode block (call constructor for inode block)
         * +
         * 
         * do diskread(block num, inode block just created) + return inode
         * 
         * ib.node[ioffset(inode)] or ib.node.offset.opennode (something like
         * 
         * one of these)
         */

        Inode n = new Inode();

        return n;

    }

    // writes and inode to the disk

    public int writeInode(int inodenum, Inode in) {

        /**
         * 
         * + check if inodenum is greater than 0. error() return-1 + get a block
         * 
         * number by call to iblock(inodenum) + check iblock is within bounds
         * 
         * like readinode + create new inodeblock(iblock) by calling constructor
         * +
         * 
         * read block off disk corresponding to blocknum from iblock store it to
         * 
         * inode block ib + update inode with correct offset from indoeblock
         * 
         * just created,(use ioffset +write back to disk + return 0
         */

        return 0;

    }

    // determines the block number of a given node from absolute block number

    public int iblock(int absblocknum) {

        /**
         * 
         * + use similar equation to inodenumber
         */

        return 0;

    }

    // Takes in file descriptor and where( ints).

    // Where is the logical block number in the file

    public int allocateBlock(int filedescript, int where) {

        /**

         * + check if space on disk (look at superblocks free list and make sure
         * that there is space in freelist + allocate a block by calling
         * allocateblock(void) + if returned block is -1, return -1 else, get
         * the inode from the file descriptor and add the block by calling
         * addblock(). return the block number
         */

        if (!ft.isValid(filedescript)) {
            error("invalid frile descriptor in allocateBlock(int,int)");
            return -1;
        }

        // Check for space in freelist
        int block = allocateBlock();
        if (block == -1) {
            error("something went wrong in allocateBlock() and it messed up allocateBlock(int, int)");
            return -1;
        }

        Inode in = ft.getInode(filedescript);
        int blocknum = addBlock(in, block, where);
        return blocknum;

    }

    public int allocateBlock() {

        /**
         * 
         * + check free space like above + if free list is null , re-cache the
         * 
         * firt block (freelist= new indirectblock(), read the freelist off the
         * 
         * disk and put it into the cached first block) + find the first
         * 
         * non-empty block onthe free list +determine the offset of the first
         * 
         * free block +special case to deal with: free list block is empty +
         * 
         * retrun free block you just picked up
         */
        
            
        if(freeList==NULL){
            freeList= new IndirectBlock();
            int f = superblock.freelist;
            disk.read(f,freeList);
        }
        //Check for space on free list
        

        return 0;

    }

    // frees given block

    public int freeBlock(int blocknum) {

        /**
         * 
         * + check input to make sure a valid block + deal with case of no free
         * 
         * blocks left (this will be first block added to empty free list) + go
         * 
         * through free list itself and figure out what the last unused element
         * 
         * is in the indirect blocks pointer data structure + add block to the
         * 
         * list using freelist.point[offset]=block + there are special cases to
         * 
         * deal with- FIGURE OUT
         */

        return 0;

    }

    /**
     * 
     * Read the block number
     * 
     * 
     * 
     * @param inode
     * 
     *            the inode
     * 
     * @param block
     * 
     *            the logical block number
     * 
     * @return the corresponding block number on the disk, 0 if not available,
     * 
     *         -1 on error
     */

    private int getBlock(Inode inode, int block) {

        // Figure out the size of file in blocks and validate the block number

        int size = (inode.fileSize + Disk.BLOCK_SIZE - 1) / Disk.BLOCK_SIZE;

        if (block < 0)

            return -1;

        if (block >= size)

            return 0;

        // Determine the level of indirection and relative indices

        // N = pointers per indirect block

        // level = the level of indirection

        // p = index of the block relative to first block of same indirection

        // level

        // i0 = index in the inode

        // i1, i2, i3 = indices in the first, second, and third indirect blocks

        // Please refer to the design document for details

        final int N = Disk.BLOCK_SIZE / 4;

        int level = 0;

        int p = 0;

        int i0 = 0;

        int i1 = 0;

        int i2 = 0;

        int i3 = 0;

        if (block <= 9) { // direct blocks

            level = 0;

            i0 = p = block;

        } else if (block <= (9 + N)) { // single indirect

            level = 1;

            p = block - 10;

            i0 = 10;

            i1 = p;

        } else if (block <= (9 + N + N * N)) { // double indirect

            level = 2;

            p = block - (10 + N);

            i0 = 11;

            i1 = p / N;

            i2 = p % N;

        } else if (block <= (9 + N + N * N + N * N * N)) { // triple indirect

            level = 3;

            p = block - (10 + N + N * N);

            i0 = 12;

            i1 = p / (N * N);

            i2 = (p / N) % N;

            i3 = p % N;

        } else {

            error("The file is too big");

            return -1;

        }

        // Level 0

        if (level == 0)

            return inode.pointer[i0];

        // Levels 1, 2, and 3

        // ib = indirect block

        IndirectBlock ib = new IndirectBlock();

        // Read first indirect block to ib

        // disk_i1 = block number of the first indirect block

        int disk_i1 = inode.pointer[i0];

        if (disk_i1 <= 0)

            return -1;

        else

            disk.read(disk_i1, ib);

        // Read first indirect block (Level 1 only)

        if (level == 1)

            return ib.pointer[i1];

        // Read second indirect block to ib

        // disk_i2 = block number of the second indirect block

        int disk_i2 = ib.pointer[i1];

        if (disk_i2 <= 0)

            return -1;

        else

            disk.read(disk_i2, ib);

        // Read second indirect block (Level 2 only)

        if (level == 2)

            return ib.pointer[i2];

        // Read third indirect block to ib - Level 3 only

        // disk_i3 = block number of the second indirect block

        int disk_i3 = ib.pointer[i2];

        if (disk_i3 <= 0)

            return -1;

        else

            disk.read(disk_i3, ib);

        // Read third indirect block

        return ib.pointer[i3];

    }

    /**
     * 
     * Add a block to the inode
     * 
     * 
     * 
     * @param inode
     * 
     *            the inode
     * 
     * @param block
     * 
     *            the block number
     * 
     * @param where
     * 
     *            logical block number in the file
     * 
     * @return 0 if successful, or -1 if error
     */

    private int addBlock(Inode inode, int block, int where) {

        // Determine the level of indirection and relative indeces

        // N = pointers per indirect block

        // level = the level of indirection

        // p = index of the block relative to first block of same indirection

        // level

        // i0 = index in the inode

        // i1, i2, i3 = indeces in the first, second, and third indirect blocks

        // Please refer to the design document for details

        final int N = Disk.BLOCK_SIZE / 4;

        int level = 0;

        int p = 0;

        int i0 = 0;

        int i1 = 0;

        int i2 = 0;

        int i3 = 0;

        if (where <= 9) {

            level = 0;

            i0 = p = where;

        } else if (where <= (9 + N)) {

            level = 1;

            p = where - 10;

            i0 = 10;

            i1 = p;

        } else if (where <= (9 + N + N * N)) {

            level = 2;

            p = where - (10 + N);

            i0 = 11;

            i1 = p / N;

            i2 = p % N;

        } else if (where <= (9 + N + N * N + N * N * N)) {

            level = 3;

            p = where - (10 + N + N * N);

            i0 = 12;

            i1 = p / (N * N);

            i2 = (p / N) % N;

            i3 = p % N;

        } else {

            error("The file is too big");

            return -1;

        }

        // debug("Adding block " + block + " to level " + level + ": p = " + p +

        // ", i0 = " + i0 + ", i1 = " + i1 + ", i2 = " + i2 + ", i3 = " + i3);

        // Level 0

        if (level == 0) {

            inode.pointer[i0] = block;

            return 0;

        }

        // Levels 1, 2, and 3

        // allocated = number of allocated blocks

        // allocatedBocks = numbers of allocated blocks

        // ib = indirect block

        int allocated = 0;

        int[] allocatedBlocks = new int[3];

        IndirectBlock ib = new IndirectBlock();

        // Read first indirect block to ib

        // disk_i1 = block number of the first indirect block

        int disk_i1 = inode.pointer[i0];

        if (disk_i1 <= 0) {

            // Allocate corresponding number of blocks

            for (int i = 0; i < level; i++) {

                int b = allocateBlock();

                if (b <= 0) {

                    // Error occured, do cleanup and exit

                    for (int j = 0; j < i; j++)

                        freeBlock(allocatedBlocks[j]);

                    return -1;

                }

                allocatedBlocks[i] = b;

                allocated++;

            }

            // Add the first one to the inode

            disk_i1 = inode.pointer[i0] = allocatedBlocks[--allocated];

            ib.clear();

        }

        else {

            // debug("addBlock: reading i1 block " + disk_i1);

            disk.read(disk_i1, ib);

        }

        // Update and write first indirect block (Level 1 only)

        if (level == 1) {

            ib.pointer[i1] = block;

            disk.write(disk_i1, ib);

            return 0;

        }

        // Read second indirect block

        // disk_i2 = block number of the second indirect block

        boolean toBeAllocated = allocated > 0;

        int disk_i2 = (toBeAllocated) ? (allocatedBlocks[--allocated])

        : (ib.pointer[i1]);

        if (toBeAllocated || (disk_i2 <= 0)) {

            if (disk_i2 <= 0) {

                if (allocated > 0) {

                    error("Internal error"); // Should never happen

                    for (int j = 0; j < allocated; j++)

                        freeBlock(allocatedBlocks[j]);

                    return -1;

                }

                // Allocate the corresponding number of blocks

                for (int i = 0; i < level - 1; i++) {

                    int b = allocateBlock();

                    if (b <= 0) {

                        // Error occured, do cleanup and exit

                        for (int j = 0; j < i; j++)

                            freeBlock(allocatedBlocks[j]);

                        return -1;

                    }

                    allocatedBlocks[i] = b;

                    allocated++;

                }

                // Add the first one to the first indirect block

                disk_i2 = ib.pointer[i1] = allocatedBlocks[--allocated];

                disk.write(disk_i1, ib);

                ib.clear();

            }

            else {

                // It was already allocated

                ib.pointer[i1] = disk_i2;

                disk.write(disk_i1, ib);

                ib.clear();

            }

        }

        else {

            // debug("addBlock: reading i2 block " + disk_i2);

            disk.read(disk_i2, ib);

        }

        // Update and write second indirect block (Level 2 only)

        if (level == 2) {

            ib.pointer[i2] = block;

            disk.write(disk_i2, ib);

            return 0;

        }

        // Read third indirect block - Level 3 only

        // disk_i3 = block number of the second indirect block

        toBeAllocated = allocated > 0;

        int disk_i3 = (toBeAllocated) ? (allocatedBlocks[--allocated])

        : (ib.pointer[i2]);

        if (toBeAllocated || (disk_i3 <= 0)) {

            if (disk_i3 <= 0) {

                if (allocated > 0) {

                    error("Internal error"); // Should never happen

                    for (int j = 0; j < allocated; j++)

                        freeBlock(allocatedBlocks[j]);

                    return -1;

                }

                // Allocate an indirect block

                int b = allocateBlock();

                if (b <= 0) {

                    // Error occured, do cleanup and exit

                    freeBlock(b);

                    return -1;

                }

                // Add it to the second indirect block

                disk_i3 = ib.pointer[i2] = b;

                disk.write(disk_i2, ib);

                ib.clear();

            }

            else {

                // It was already allocated

                ib.pointer[i2] = disk_i3;

                disk.write(disk_i2, ib);

                ib.clear();

            }

        }

        else {

            // debug("addBlock: reading i3 block " + disk_i3);

            disk.read(disk_i3, ib);

        }

        // Update and write third indirect block

        ib.pointer[i3] = block;

        disk.write(disk_i3, ib);

        return 0;

    }

    /**
     * 
     * 
     * 
     * Free an indirect block
     * 
     * @param block
     * 
     *            the block number to free
     * 
     * 
     * 
     * @param level
     * 
     *            the level of indirection (0 == not an indirect block)
     * 
     * 
     * 
     * @return number of data blocks freed if success, or -1 on error
     * 
     * 
     */

    private int freeIndirect(int block, int level) {

        // Check input

        if (block == 0)

            return 0;

        if (block < 0)

            return -1;

        if (level < 0)

            return -1;

        // Level 0 (just free the block)

        if (level == 0) {

            return (freeBlock(block) < 0) ? (-1) : (1);

        }

        // Traverse through the list of blocks and free all of them

        // count = the number of blocks freed

        // ib = the indirect block

        int count = 0;

        IndirectBlock ib = new IndirectBlock();

        // Read the block

        disk.read(block, ib);

        // Traverse it

        for (int i = 0; i < Disk.BLOCK_SIZE / 4; i++) {

            if (ib.pointer[i] <= 0)

                continue;

            // Free the block

            int r = freeIndirect(ib.pointer[i], level - 1);

            // Update count

            if (r > 0) {

                count += r;

            }

        }

        freeBlock(block);

        return count;

    }

}

